﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace GenericLibrary.Modeling.DynamicAgent {

    public abstract class Node : IDynamicAgent {

        protected abstract IEnumerable<Relay> OutputRelays { get; }

        private double _potential;
        private double _positiveDecayConstant;
        private double _negativeDecayConstant;

        public double PositiveDecayConstant {
            get {
                return this._positiveDecayConstant;
            }
            set {
                try {
                    if( value > -1 && value < 0 ) {
                        this._positiveDecayConstant = value;
                    } else {
                        throw new Exception();
                    }
                } catch( Exception ) {
                    Debug.Assert( false );
                    throw;
                }
            }
        }

        public double NegativeDecayConstant {
            get {
                return this._negativeDecayConstant;
            }
            set {
                try {
                    if( value > -1 && value < 0 ) {
                        this._negativeDecayConstant = value;
                    } else {
                        throw new Exception();
                    }
                } catch( Exception ) {
                    Debug.Assert( false );
                    throw;
                }
            }
        }

        protected Node( double positiveDecayConstant, double negativeDecayConstant ) {
            this.PositiveDecayConstant = positiveDecayConstant;
            this.NegativeDecayConstant = negativeDecayConstant;
            this._potential = 0;
        }

        public void Input( double inputPotential ) {
            this._potential += inputPotential;
            if( this._potential > 1 ) {
                this._potential = 1;
            } else if( this._potential < -1 ) {
                this._potential = -1;
            }
        }

        #region IDynamicAgent Members

        void IDynamicAgent.Synchronize( double timeStep ) {
            try {
                if( this._potential > 0 ) {
                    this._potential = Math.Exp( Math.Log( this._potential, Math.E ) + timeStep * this._positiveDecayConstant );
                } else if( this._potential < 0 ) {
                    this._potential = -Math.Exp( Math.Log( -this._potential, Math.E ) + timeStep * this._negativeDecayConstant );
                }
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        void IDynamicAgent.Output( IEnumerable<IDynamicAgent> collection ) {
            try {
                foreach( Relay relay in this.OutputRelays ) {
                    relay.Input();
                }
                this._potential = -1;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        double IDynamicAgent.OutputDelay {
            get {
                return ( this._potential == 1 ) ? 0 : double.PositiveInfinity;
            }
        }

        #endregion
    }

    public abstract class Relay : IDynamicAgent {

        protected abstract IEnumerable<Node> OutputNodes { get; }

        private double _outputDelay;
        private double _amplifyConstant;
        private double _delayConstant;

        protected Relay( double amplifyConstant, double delayConstant ) {
            this.AmplifyConstant = amplifyConstant;
            this.DelayConstant = delayConstant;
            this._outputDelay = double.PositiveInfinity;
        }

        public double AmplifyConstant {
            get {
                return this._amplifyConstant;
            }
            set {
                try {
                    if( value > -1 && value < 1 ) {
                        this._amplifyConstant = value;
                    } else {
                        throw new Exception();
                    }
                } catch( Exception ) {
                    Debug.Assert( false );
                    throw;
                }
            }
        }

        public double DelayConstant {
            get {
                return this._delayConstant;
            }
            set {
                try {
                    if( value > 0 && value < 1 ) {
                        this._delayConstant = value;
                    } else {
                        throw new Exception();
                    }
                } catch( Exception ) {
                    Debug.Assert( false );
                    throw;
                }
            }
        }

        internal void Input() {
            if( double.IsPositiveInfinity( this._outputDelay ) ) {
                this._outputDelay = this._delayConstant;
            }
        }

        #region IDynamicAgent Members

        void IDynamicAgent.Synchronize( double timeStep ) {
            this._outputDelay -= timeStep;
        }

        void IDynamicAgent.Output( IEnumerable<IDynamicAgent> collection ) {
            try {
                foreach( Node node in this.OutputNodes ) {
                    node.Input( this._amplifyConstant );
                }
                this._outputDelay = double.PositiveInfinity;
            } catch( Exception ) {
                Debug.Assert( false );
                throw;
            }
        }

        double IDynamicAgent.OutputDelay {
            get {
                return this._outputDelay;
            }
        }

        #endregion
    }
}
